 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
 #include <linux/fs.h>        // 包含 struct inode 和 struct file 定义
#include <linux/cdev.h>      // 字符设备相关
#include <linux/uaccess.h>   // 包含 copy_from_user 声明
#include <linux/device.h>   // 包含 class_create 和 device_create 的声明
#include <asm/io.h>

#define DEV_NAME "EmbedLedDev"
#define DEV_CNT (1)
	
#define GPIOC_BASE (0xC001C000)

static struct class *led_class = NULL;
static struct device *led_device = NULL;

struct led_chrdev {
	struct cdev dev;
	void __iomem *gpioc_va_base;
	void __iomem *gpioc_va_out;    
	void __iomem *gpioc_va_enb;
	void __iomem *gpioc_va_fn0;	
	void __iomem *gpioc_va_fn1;			

	unsigned int led_pin; // 引脚
};

static struct led_chrdev led_cdev[DEV_CNT] = {
        {.led_pin = 0xC7},
};

static int chr_dev_open(struct inode *inode, struct file *filp)
{
	unsigned int val = 0;
	
	
    struct led_chrdev *led_cdev = (struct led_chrdev *)container_of(inode->i_cdev, struct led_chrdev, dev);
    filp->private_data = container_of(inode->i_cdev, struct led_chrdev, dev);

	printk("open\n");

    //设置做为GPIO来使用
    val = ioread32(led_cdev->gpioc_va_fn0);
    val |= ((1<<14));
	iowrite32(val,led_cdev->gpioc_va_fn0);
	
	//设置GPIOC为输出模式
	val = ioread32(led_cdev->gpioc_va_enb);
    val |= (1<<7); 
    iowrite32(val,led_cdev->gpioc_va_enb);

	//默认设置为灭
    val = ioread32(led_cdev->gpioc_va_out);
    val |= (1<<7);
    iowrite32(val,led_cdev->gpioc_va_out);
	
	return 0;
}

static int chr_dev_release(struct inode *inode, struct file *filp)
{
	printk("release\n");
	return 0;
}

static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos)
{
    unsigned long val = 0;
    char ret = 0;
    struct led_chrdev *led_cdev = (struct led_chrdev *)filp->private_data;

    get_user(ret, buf);
    val = ioread32(led_cdev->gpioc_va_out);
	printk("cmd=%c\n", ret);
    if (ret == '0')
	{
		val |= (1<<7);
    }
    else
	{
		val &= ~(1<<7);
    }
    iowrite32(val, led_cdev->gpioc_va_out);

    return count;
}

static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos)
{
   
   return count;
}


static struct file_operations chr_dev_fops = 
{
	.owner = THIS_MODULE,
	.open = chr_dev_open,
	.release = chr_dev_release,
	.write = chr_dev_write,
	.read = chr_dev_read,
};

	


//定义字符设备的设备号
static dev_t devno;
//定义字符设备结构体chr_dev
static struct cdev chr_dev;
struct resource * led_resource = NULL;

static int __init chrdev_init(void)
{
	int ret = 0;
	int i=0;
	 dev_t cur_dev;
	printk("led dev init\n");
	
	//[8] 申请物理内存区 ---SFR GPIOCOUT 0XC001C000  4KB ----> 0x1000
	//struct resource *request_mem_region(resource_size_t start, resource_size_t n,const char *name)
	led_resource = request_mem_region(0xC001C000,0x1000,"GPIOC_MEM");
	if(led_resource == NULL)
	{
		printk("request_mem_region error\n");
		ret = -EBUSY;
		goto request_mem_region_error;
	}
	
	
	led_cdev[0].gpioc_va_base = ioremap(0xC001C000,0x1000);
	led_cdev[0].gpioc_va_out = led_cdev[0].gpioc_va_base + 0x00;
	led_cdev[0].gpioc_va_enb = led_cdev[0].gpioc_va_base + 0x04;
	led_cdev[0].gpioc_va_fn0 = led_cdev[0].gpioc_va_base + 0x20;
	led_cdev[0].gpioc_va_fn1 = led_cdev[0].gpioc_va_base + 0x24;
	
	//采用动态分配的方式，获取设备编号，次设备号为0，
	//设备名称为EmbedCharDev，可通过命令cat /proc/devices查看
	//DEV_CNT为1，当前只申请一个设备编号
	ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);
	if (ret < 0) 
	{
		printk("fail to alloc devno\n");
		goto alloc_err;
	}
	
	//创建一个led_class
	//struct class *class_create(struct module *owner, const char *name)
	led_class = class_create(THIS_MODULE,"led_chrdev");
	if(led_class == NULL)
	{
		printk("class_create error\n");
		//释放以前申请成功的资源
		//void unregister_chrdev_region(dev_t from, unsigned count)释放申请的设备号
		//void cdev_del(struct cdev *p) 从链表中，把该设备删除
		ret = -EBUSY;
		goto class_create_error;
	}
	
	for (; i < DEV_CNT; i++) 
	{
		cdev_init(&led_cdev[i].dev, &chr_dev_fops);
		led_cdev[i].dev.owner = THIS_MODULE;

		cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + i);

		cdev_add(&led_cdev[i].dev, cur_dev, 1);

		device_create(led_class, NULL, cur_dev, NULL,
								DEV_NAME "%d", i);
	}
	

#if 0
	//第二步
	//关联字符设备结构体cdev与文件操作结构体file_operations
	cdev_init(&chr_dev, &chr_dev_fops);
	//第三步
	//添加设备至cdev_map散列表中
	ret = cdev_add(&chr_dev, devno, DEV_CNT);
	if (ret < 0) 
	{
		printk("fail to add cdev\n");
		goto add_err;
	}
	
	
	//[7] 创建一个device
	//struct device *device_create(struct class *class, struct device *parent,
	//				 dev_t devt, void *drvdata, const char *fmt, ...)
	
	led_device = device_create(led_class,NULL,devno,NULL,"chrdev"); //设备文件的名字 /dev/目录下
	if(led_device == NULL)
	{
		printk("device_create error\n");
		ret = -EBUSY;
		goto device_create_error;
		
	}
#endif
	printk("init ok\n");
	return 0;

//device_create_error:
	class_destroy(led_class);
	led_device = NULL;
class_create_error:
	cdev_del(&chr_dev);
	led_class = NULL;

//add_err:
	//添加设备失败时，需要注销设备号
	unregister_chrdev_region(devno, DEV_CNT);
	alloc_err:
	
request_mem_region_error:
	device_destroy(led_class,devno);
	led_resource = NULL;
	return ret;
}
module_init(chrdev_init);

static void __exit chrdev_exit(void)
{
	int i;
	dev_t cur_dev;
	printk("led chrdev exit\n");
	release_mem_region(0xC001C000,0x1000);
	led_resource = NULL;

	for (i = 0; i < DEV_CNT; i++) 
	{
		iounmap(led_cdev[i].gpioc_va_base);             // 释放数据寄存器虚拟地址
	}

	for (i = 0; i < DEV_CNT; i++) 
	{
		cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + i);

		device_destroy(led_class, cur_dev);

		cdev_del(&led_cdev[i].dev);

	}
	unregister_chrdev_region(devno, DEV_CNT);
	class_destroy(led_class);
	#if 0
	printk("chrdev exit\n");
	device_destroy(led_class,devno);
	
	class_destroy(led_class);
	led_device = NULL;

	unregister_chrdev_region(devno, DEV_CNT);
	cdev_del(&chr_dev);
	#endif
}
module_exit(chrdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("hello module");
MODULE_ALIAS("test_module");

